/*
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * $Id: Analyzer.java 663 2007-07-02 14:53:41Z jacek.kolezynski $
 */
package logmatcher.analyzers;

import logmatcher.analyzers.Sequence.State;
import logmatcher.executor.Executor;

import logmatcher.report.ReportEvent;
import logmatcher.report.ReportHandler;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.apache.log4j.spi.LoggingEvent;

import java.util.Timer;
import java.util.TimerTask;
import java.util.regex.Matcher;
import java.util.regex.Pattern;


/**
*  Abstract class for matching elements. Supplies a skeleton of basic operations and algorithm of processing
*  messages.
*
*  @version $Revision: 663 $
 */
public class Analyzer {
    //~ Static fields/initializers -------------------------------------------------------------------------------------

    /** Class logger. */
    private static final Log LOGGER = LogFactory.getLog(Analyzer.class);

    /** End of test log message. */
    static final String END_OF_TEST = "--- END ---";

    //~ Instance fields ------------------------------------------------------------------------------------------------

    /** String id pattern used to extract pair identifier. */
    protected String id;

    /** Pattern used to extract pair identifier. */
    protected Pattern idPattern;

    /** Determines if to log messages containing id, but not matching any of sequence elements. */
    protected boolean showInvalidMessages;

    /** Flag telling if the analysis is stopped. */
    protected boolean stopped = true;

    /** Sequence to be analyzed. */
    private Sequence sequence;

    /** Currently analyzed */
    private Sequence analyzedSequence;

    /** The executor object. */
    private Executor executor;

    /** The report handler object. */
    private ReportHandler reportHandler;
    
    private long timeout;
    
    private TimerTask timeoutTask;

    //~ Constructors ---------------------------------------------------------------------------------------------------

/**
     * Creates a new PairMatchingAnalyzer object.
     */
    public Analyzer() {
    }

    //~ Methods --------------------------------------------------------------------------------------------------------

    /**
    *  
    DOCUMENT ME!
    *
    *  @return TODO: DOCUMENT ME!
    *
    *  @todo DOCUMENT ME!
    */
    public Executor getExecutor() {
        return executor;
    }


    /**
    *  DOCUMENT ME!
    *
    *  @return TODO: DOCUMENT ME!
    *
    *  @todo DOCUMENT ME!
    */
    public String getIdPattern() {
        return id;
    }


    /**
    *  
    DOCUMENT ME!
    *
    *  @return TODO: DOCUMENT ME!
    *
    *  @todo DOCUMENT ME!
    */
    public ReportHandler getReportHandler() {
        return reportHandler;
    }


    /**
    *  Returns curretn sequence.
    *
    *  @return sequence used for analysis.
    */
    public Sequence getSequence() {
        return sequence;
    }


    /**
    *  Gets a deep copy of the analyzed sequence.
    *
    *  @param id the id to be set to the sequence
    *
    *  @return deep copy of the analyzed sequence
    */
    public Sequence getSequenceCopy(String id) {
        Sequence s = (Sequence)sequence.cloneWithId(id);

        return s;
    }


    /**
    *  DOCUMENT ME!
    *
    *  @return TODO: DOCUMENT ME!
    *
    *  @todo DOCUMENT ME!
    */
    public boolean isShowInvalidMessages() {
        return showInvalidMessages;
    }


    /**
    *  Receives messages and performs analysis.
    *
    *  @param message the message
    */
    public synchronized void receiveMessage(Object message) {
        if (stopped) {
            return;
        }

        String msg;

        if (message instanceof LoggingEvent) {
            LoggingEvent event = (LoggingEvent)message;
            msg = String.valueOf(event.getMessage());
        } else {
            msg = (String)message;
        }

        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("The message = " + msg);
        }

        Matcher matcher = idPattern.matcher(msg);

        if (matcher.find()) {
            if (LOGGER.isDebugEnabled()) {
                if (id == null) {
                    id = matcher.group();
                }

                LOGGER.debug("This LogEvent has id = " + id + ", it matches the id pattern " + idPattern.pattern());
            }

            if (analyzedSequence == null) {
                analyzedSequence = getSequenceCopy(id);
                analyzedSequence.setAnalyzer(this);
                analyzedSequence.start();
            }

            analyzedSequence.check(msg);
        } else {
            if (showInvalidMessages) {
                LOGGER.warn("This message does not match the id, skipping this message: " + msg);
            }
        }
    }


    /**
    *  Waits for the analyzed sequence to finish and check it result, then inform executor and report handler
    *  about it.
    */
    public synchronized void sequenceCompleted(Sequence s){
        LOGGER.debug("Waiting for the sequence to complete...");
        
        if(timeoutTask != null)
        	timeoutTask.cancel();
        timeout = 0;

        Sequence.State result = analyzedSequence.checkResult();
        LOGGER.debug("Sequence is completed, analyzing the result...");
    
        ReportEvent.Type reportResult;
        String message = executor.getTestCaseName();

        if (result == Sequence.State.FAILURE) {
            LOGGER.debug("Sequence matching FAILED.");
            reportResult = ReportEvent.Type.FAILED;
            message += " - analyzer: Test failed";

            reportHandler.receiveEvent(new ReportEvent(reportResult, message));
            LOGGER.info(END_OF_TEST);
            executor.taskEnded();
        } else if (result == Sequence.State.SUCCESS) {
            LOGGER.debug("Sequence matching COMPLETED SUCCESSFULY.");
            reportResult = ReportEvent.Type.PASSED;
            message += " - analyzer: Test passed";

            reportHandler.receiveEvent(new ReportEvent(reportResult, message));
            LOGGER.info(END_OF_TEST);
            executor.taskEnded();
        }
        analyzedSequence = null;
        stop();
    }


    /**
    *  
    DOCUMENT ME!
    *
    *  @param executor TODO: DOCUMENT ME!
    *
    *  @todo DOCUMENT ME!
    */
    public void setExecutor(Executor executor) {
        this.executor = executor;
    }


    /**
    *  Sets the id pattern.
    *
    *  @param id id pattern
    */
    public void setIdPattern(String id) {
        this.idPattern = Pattern.compile(id);
    }


    /**
    *  
    DOCUMENT ME!
    *
    *  @param reportHandler TODO: DOCUMENT ME!
    *
    *  @todo DOCUMENT ME!
    */
    public void setReportHandler(ReportHandler reportHandler) {
        this.reportHandler = reportHandler;
    }


    /**
    *  Sets the sequence to be analyzed.
    *
    *  @param sequence sequence to be analyzed
    */
    public void setSequence(Sequence sequence) {
        this.sequence = sequence;
    }


    /**
    *  Sets if invalid (not matched) elements are shown.
    *
    *  @param showInvalidMessages flag, if true than invalid messages will be  shown, if false then they will be just
    *         skipped
    */
    public void setShowInvalidMessages(boolean showInvalidMessages) {
        this.showInvalidMessages = showInvalidMessages;
    }


    /**
    *  Starts the analysis.
    */
    public void start() {
        stopped = false;
        
        if(timeout > 0){
        	Timer timer = new Timer();
            timeoutTask = new TimerTask() {
                    @Override
                    public void run() {
                        LOGGER.info("Analyzer timeout, FAILURE!");
                        stopped = true;
                        ReportEvent.Type reportResult = ReportEvent.Type.FAILED;
                        String message = executor.getTestCaseName();
                        message += " - analyzer: Timeout, test failed";

                        reportHandler.receiveEvent(new ReportEvent(reportResult, message));
                        LOGGER.info(END_OF_TEST);
                        executor.taskEnded();
                        
                    }
                };

            timer.schedule(timeoutTask, timeout);
        }
    }


    /**
    *  Stops the analysis. Stops collecting events and stops all elements.
    */
    public void stop() {
        stopped = true;
        if(timeoutTask != null)
        	timeoutTask.cancel();
        timeout = 0;

        if (analyzedSequence != null) {
            analyzedSequence.stop();
            analyzedSequence = null;
        }
    }
    
    public void setTimeout(long timeout){
    	this.timeout = timeout;
    }
}
